﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Web.Services.Protocols;
using System.Windows.Forms;
using System.Xml.Linq;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.Sdk.Metadata;
using Microsoft.Crm.Sdk.Query;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Crm.SdkTypeProxy.Metadata;
using OrbitOne.CRM;
using AuthenticationType = OrbitOne.CRM.AuthenticationType;

namespace OrbitOne.CRMUpdater.CRM
{
    public class Updator
    {
        #region Events
        public event EventHandler<UpdaterErrorEventArgs> ErrorOccurred;
        public void OnError(Exception ex, int rowindex, int colindex)
        {
            if (ErrorOccurred != null)
                ErrorOccurred(this, new UpdaterErrorEventArgs(ex, rowindex, colindex));
        }

        public event EventHandler<RowUploadCompletedEventArgs> RowUploadCompleted;
        public void OnRowUploadCompleted(int rowindex, string errorMessage)
        {
            if (RowUploadCompleted != null)
                RowUploadCompleted(this, new RowUploadCompletedEventArgs(rowindex, errorMessage));
        }
        #endregion

        #region Private enum
        private enum QueryType
        {
            savedquery,
            userquery
        }
        #endregion

        #region Private Variables
        private Thread m_thread;
        #endregion

        #region Properties
        public Exception Exception { get; private set; }
        public string Url { get; private set; }
        public string UserName { get; private set; }
        public string Password { get; private set; }
        public string Domain { get; private set; }
        public AuthenticationType AuthenticationType { get; private set; }
        public string Instance { get; private set; }

        public List<string> Entities { get { return Metadata.Keys.ToList(); } }

        public CrmServiceWrapper Service { get; private set; }
        public MetadataService MetaDataService { get; private set; }
        public int ErrorCounter { get; private set; }
        public string Status { get; private set; }
        public bool Error { get; private set; }
        public bool Finished { get; private set; }
        public Dictionary<string, EntityMetadata> Metadata { get; private set; }
        #endregion

        #region Constructor
        public Updator(Connection conn)
            : this(conn.Server, conn.UserName, conn.Password, conn.Domain, conn.Authentication, conn.Instance)
        {

        }
        public Updator(string url, string username, string password, string domain, int auth, string instance)
        {
            Url = url;
            UserName = username;
            Password = password;
            Domain = domain;
            AuthenticationType = (AuthenticationType)auth;
            Instance = instance;
            ErrorCounter = -1;
        }
        #endregion

        #region Public Methods
        public void Login()
        {
            m_thread = new Thread(startThread);
            m_thread.Start();
        }
        public void Abort()
        {
            if (m_thread != null && (m_thread.IsAlive || !Finished))
                m_thread.Abort();
        }

        public List<QueryItem> GetQueries(string entity)
        {
            var result = new List<QueryItem>();
            result.AddRange(retrieveQueryCollection(QueryType.savedquery, entity).BusinessEntities.Cast<savedquery>().Select(b => new QueryItem(b.name, b.fetchxml, b.layoutxml)));
            result.AddRange(retrieveQueryCollection(QueryType.userquery, entity).BusinessEntities.Cast<userquery>().Select(b => new QueryItem(b.name, b.fetchxml, b.layoutxml)));
            return result.OrderBy(e => e.SavedQueryName).ToList();
        }

        public DataTable GetRecords(QueryItem item, string entity)
        {
            ErrorCounter = -1;
            try
            {
                var dt = new DataTable();
                RetrieveMultipleRequest request;
                if (item != null)
                {
                    #region No query specified
                    var response = (FetchXmlToQueryExpressionResponse)Service.Execute(new FetchXmlToQueryExpressionRequest { FetchXml = item.SavedQueryXml });
                    response.Query.EntityName = entity;
                    //response.Query.ColumnSet.AddColumn("modifiedon");
                    request = new RetrieveMultipleRequest
                                {
                                    Query = response.Query,
                                    ReturnDynamicEntities = true
                                };

                    var attr = ((ColumnSet)response.Query.ColumnSet).Attributes.ToArray();

                    var toAdd = attr.FirstOrDefault(a => a.ToString() == Metadata[entity].PrimaryKey).ToString();
                    if (!string.IsNullOrEmpty(toAdd))
                    {
                        dt.Columns.Add(toAdd);
                    }
                    //toAdd = attr.FirstOrDefault(a => a.ToString() == "modifiedon").ToString();
                    //if (!string.IsNullOrEmpty(toAdd))
                    //{
                    //    dt.Columns.Add(toAdd);
                    //}
                    var doc = XDocument.Parse(item.LayOutXml);
                    var cells = doc.Descendants("cell")
                                   .ToList()
                                   .Where(e => e.Attribute("name") != null)
                                   .Select(e => e.Attribute("name").Value)
                                   .ToList();
                    dt.Columns.AddRange(cells.Where(_ => !dt.Columns.Contains(_))
                                             .Select(_ => new DataColumn(_))
                                             .ToArray());
                    #endregion
                }
                else
                {
                    #region Query specified
                    request = new RetrieveMultipleRequest
                        {
                            ReturnDynamicEntities = true,
                            Query = new QueryExpression
                            {
                                ColumnSet = new AllColumns(),
                                EntityName = entity
                            }
                        };

                    var attr = Metadata[entity].Attributes;
                    dt.Columns.AddRange(attr.Where(_ => !dt.Columns.Contains(_.LogicalName)).Select(_ => new DataColumn(_.LogicalName)).ToArray());
                    #endregion
                }
                var count = 5000;
                var page = 0;
                var dataColumns = dt.Columns.Cast<DataColumn>().Select(_ => _.ColumnName).ToList();
                var pagingcookie = "";
                while (count % 5000 == 0 && count != 0)
                {
                    ((QueryExpression)request.Query).PageInfo = new PagingInfo
                            {
                                PagingCookie = pagingcookie,
                                Count = 5000,
                                PageNumber = ++page
                            };
                    var fqresponse = (RetrieveMultipleResponse)Service.Execute(request);
                    count = fqresponse.BusinessEntityCollection.BusinessEntities.Count;
                    fqresponse.BusinessEntityCollection
                              .BusinessEntities
                              .OfType<DynamicEntity>()
                              .ToList()
                              .ForEach(de =>
                              {
                                  var items = new object[dataColumns.Count];
                                  de.Properties.Where(prop => dataColumns.Contains(prop.Name))
                                               .Select(prop => MapPropertyToString(prop, dataColumns.IndexOf(prop.Name)))
                                               .ToList()
                                               .ForEach(pair => items[pair.Key] = pair.Value);
                                  dt.Rows.Add(items);
                              });
                    pagingcookie = fqresponse.BusinessEntityCollection.PagingCookie;
                }

                return dt;
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return null;
            }
        }
        private static readonly Func<Property, int, KeyValuePair<int, string>> MapPropertyToString =
        (prop, i) =>
        {
            if (prop == null)
                return new KeyValuePair<int, string>(i, "");
            string field;
            var pi = prop.GetType().GetProperty("Value");
            var cft = pi.GetValue(prop, null).GetType().Name;
            #region Get property type and map
            try
            {
                field = FuncDict[cft](prop, pi);
            }
            catch
            {
                field = pi.GetValue(prop, null).ToString();
            }
            #endregion

            return new KeyValuePair<int, string>(i, field);
        };
        private static readonly Func<Property, string, string> MapPropertyToString2 =
        (prop, i) =>
        {
            if (prop == null)
                return "";
            string field;
            var pi = prop.GetType().GetProperty("Value");
            var cft = pi.GetValue(prop, null).GetType().Name;
            #region Get property type and map
            try
            {
                field = FuncDict[cft](prop, pi);
            }
            catch
            {
                field = pi.GetValue(prop, null).ToString();
            }
            #endregion

            return string.Format("({0},{1},{2})", i, prop.ToString().Replace("Microsoft.Crm.Sdk.", ""), field);
        };
        private static readonly IDictionary<string, Func<Property, PropertyInfo, string>> FuncDict =
                new Dictionary<string, Func<Property, PropertyInfo, string>>
                    {
                            {typeof (String).Name, (prop, pi) => (string) pi.GetValue(prop, null)},
                            {typeof (Key).Name, (prop, pi) => ((Key) pi.GetValue(prop, null)).Value.ToString()},
                            {typeof (Guid).Name, (prop, pi) => ((Guid) pi.GetValue(prop, null)).ToString()},
                            {typeof (DateTime).Name, (prop, pi) => ((DateTime) pi.GetValue(prop, null)).ToString(CultureInfo.InvariantCulture)},
                            {typeof (CrmDateTime).Name, (prop, pi) =>
                                                                   {
                                                                       var result = "";
                                                                       var dateTime =
                                                                               (CrmDateTime) pi.GetValue(prop, null);
                                                                       if (prop.Name == "modifiedon")
                                                                       {
                                                                           result = dateTime.Value;
                                                                       }
                                                                       else if (dateTime != null)
                                                                       {
                                                                           result = dateTime.UserTime.ToString();
                                                                       }
                                                                       return result;
                                                                   }},
                            {typeof (CrmBoolean).Name, (prop, pi) => ((CrmBoolean) pi.GetValue(prop, null)).Value ? "1" : "0"},
                            {typeof (Picklist).Name, (prop, pi) => ((Picklist) pi.GetValue(prop, null)).Value.ToString()},
                            {typeof (Status).Name, (prop, pi) => ((Status) pi.GetValue(prop, null)).Value.ToString()},
                            {typeof (CrmNumber).Name, (prop, pi) => ((CrmNumber) pi.GetValue(prop, null)).Value.ToString()},
                            {typeof (CrmMoney).Name, (prop, pi) =>
                                                                {
                                                                    var amt = (CrmMoney) pi.GetValue(prop, null);
                                                                    var result = amt.Value.ToString();
                                                                    if (result != null)
                                                                        result = result.TrimEnd('0');
                                                                    return result;
                                                                }},
                            {typeof (CrmFloat).Name, (prop, pi) =>
                                                                {
                                                                    var amt = (CrmFloat) pi.GetValue(prop, null);
                                                                    var result = amt.Value.ToString();
                                                                    if (result != null)
                                                                        result = result.TrimEnd('0');
                                                                    return result;
                                                                }},
                            {typeof (CrmDecimal).Name, (prop, pi) =>
                                                                  {
                                                                      var amt = (CrmDecimal) pi.GetValue(prop, null);
                                                                      var result = amt.Value.ToString();
                                                                      if (result != null)
                                                                          result = result.TrimEnd('0');
                                                                      return result;
                                                                  }},
                            {typeof (Owner).Name, (prop, pi) =>
                                                             {
                                                                 var own = (Owner) pi.GetValue(prop, null);
                                                                 return own.type + "," + own.Value;
                                                             }},
                            {typeof (Lookup).Name, (prop, pi) =>
                                                              {
                                                                  var lu = (Lookup) pi.GetValue(prop, null);
                                                                  return lu.type + "," + lu.Value;
                                                              }},
                            {typeof (Customer).Name, (prop, pi) =>
                                                                {
                                                                    var cust = (Customer) pi.GetValue(prop, null);
                                                                    return cust.type + "," + cust.Value;
                                                                }},
                            {typeof(DynamicEntity[]).Name,(prop,pi)=>
                                      {
                                          var pl = (DynamicEntity[]) pi.GetValue(prop, null);
                                          var sb = new StringBuilder();


                                          pl.ToList().ForEach(e =>
                                                                  {
                                                                      sb.Append(e.Name + "{");
                                                                      e.Properties.ToList().ForEach(
                                                                          p =>
                                                                          sb.Append(MapPropertyToString2(p, p.Name)));
                                                                      sb.Append("};");
                                                                  });
                                          return sb.ToString();
                                      }}};

        public void UploadToCrm(DataTable table, string entity)
        {
            ErrorCounter = 0;
            ThreadPool.QueueUserWorkItem(toCrm, new object[] { table, entity });
        }
        #endregion

        #region Private Methods
        private void startThread()
        {
            createServices();
            retrieveMetaData();

            if (!Error)
                Status = "Initialization completed";

            Finished = true;
        }
        private void createServices()
        {
            Status = "Creating Services ... ";
            try
            {
                Service = new CrmServiceWrapper(UserName, Password, Domain, Url, Instance, "crm.dynamics.com", AuthenticationType);
                MetaDataService = Service.MetaDataService;
                Service.Execute(new WhoAmIRequest());
            }
            catch (SoapException e)
            {
                if (e.Detail != null)
                {
                    Status = e.Detail.InnerText;
                }
                else
                {
                    Status = e.Message;
                }
                Exception = e;
                Error = true;
            }
            catch (Exception e)
            {
                Exception = e;
                Status = e.Message;
                Error = true;
            }
        }
        private void retrieveMetaData()
        {
            Status = "Getting Entities ... ";
            try
            {
                var metadataResponse = (RetrieveAllEntitiesResponse)MetaDataService.Execute(
                                            new RetrieveAllEntitiesRequest
                                            {
                                                MetadataItems = MetadataItems.IncludeAttributes
                                            });
                Metadata = metadataResponse.CrmMetadata.Cast<EntityMetadata>().ToDictionary(_ => _.LogicalName, _ => _);
                Error = false;
            }
            catch (Exception e)
            {
                Status = e.Message;
                Error = true;
            }
        }
        private BusinessEntityCollection retrieveQueryCollection(QueryType type, string entity)
        {
            var query = getQueryExpression(type, entity);
            return Service.RetrieveMultiple(query);
        }

        private QueryBase getQueryExpression(QueryType type, string entity)
        {
            var colset = new ColumnSet();
            colset.AddColumns(new[] { "name", type + "id", "fetchxml", "layoutxml" });
            var filtExp = new FilterExpression
            {
                FilterOperator = LogicalOperator.And
            };
            filtExp.Conditions.Add(new ConditionExpression
            {
                AttributeName = "returnedtypecode",
                Operator = ConditionOperator.Equal,
                Values = new object[] { Metadata[entity].ObjectTypeCode.Value }
            });
            filtExp.Conditions.Add(new ConditionExpression
            {
                AttributeName = "querytype",
                Operator = ConditionOperator.Equal,
                Values = new object[] { 0 }
            });
            filtExp.Conditions.Add(new ConditionExpression
            {
                AttributeName = "fetchxml",
                Operator = ConditionOperator.NotNull
            });
            return new QueryExpression
                       {
                           EntityName = type.ToString(),
                           ColumnSet = colset,
                           Criteria = filtExp
                       };
        }

        private void toCrm(object o)
        {
            ErrorCounter = 0;
            Status = "";
            Finished = false;
            var param = (object[])o;
            var table = (DataTable)param[0];
            var entity = (string)param[1];

            var toUpload = new Dictionary<DynamicEntity, int>();
            var attributes = Metadata[entity].Attributes;
            for (var r = 0; r < table.Rows.Count; r++)
            {
                if (table.Rows[r].ItemArray.All(i => i.GetType() == typeof(DBNull) || string.IsNullOrEmpty(i.ToString().Replace("\r", ""))))
                {
                    continue;
                }

                var de = new DynamicEntity(entity);
                var hasError = false;
                for (var i = 0; i < table.Columns.Count; i++)
                {
                    try
                    {
                        var val = ((table.Rows[r][i].GetType() == typeof(DBNull) ? null : table.Rows[r][i].ToString()) ?? "").Replace("\r", "");
                        var attribute = attributes.SingleOrDefault(a => a.LogicalName == table.Columns[i].ToString().Replace("\r", ""));
                        if (attribute != null)
                        {
                            //memo can be ntext. Sometimes this can be set.
                            if (attribute.AttributeType.Value.ToString() == "Memo")
                                attribute.AttributeType.Value = AttributeType.String;
                            if (attribute.AttributeType.Value.ToString() != "Virtual")
                            {
                                var prop = MapStringToProperties[attribute.AttributeType.Value.ToString()](val);

                                if (prop != null)
                                {
                                    prop.Name = table.Columns[i].ToString().Replace("\r", "");
                                    de.Properties.Add(prop);
                                }
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        Exception = e;
                        OnError(e, r, i);
                        hasError = true;
                    }
                }
                if (!hasError)
                    toUpload.Add(de, r);
            }

            Error = false;
            toUpload.ToList().ForEach(row => uploadRow(row, 10));

            Finished = true;
        }

        private void uploadRow(KeyValuePair<DynamicEntity, int> row, int retryCount)
        {
            while (retryCount > 0)
            {
                try
                {
                    uploadRow(row);
                    retryCount = 0;
                }
                catch (Exception ex)
                {
                    retryCount--;
                    if (retryCount != 0)
                    {
                        Thread.Sleep(1000);
                    }
                    else
                    {
                        var soap = ex as SoapException;
                        if (soap != null)
                        {
                            Exception = soap;
                            OnRowUploadCompleted(row.Value, soap.Detail.InnerText);
                            ErrorCounter++;
                            Error = true;
                        }
                        else
                        {
                            Exception = ex;
                            OnRowUploadCompleted(row.Value, ex.Message);
                            ErrorCounter++;
                            Error = true;
                        }
                    }
                }
            }
        }


        private void uploadRow(KeyValuePair<DynamicEntity, int> row)
        {
            if (row.Key.Properties.Any(p => p.GetType() == typeof(KeyProperty)))
            {
                Status = "Processing "
                         + row.Key.Name + " "
                         + ((KeyProperty)row.Key
                                             .Properties
                                             .First(p => p.GetType() == typeof(KeyProperty)))
                               .Value
                               .Value;
                Service.Update(row.Key);
            }
            else
            {
                Status = "Processing "
                         + row.Key.Name + " [NEW ENTITY]";
                Service.Create(row.Key);
            }
            OnRowUploadCompleted(row.Value, null);
        }
        #endregion

        public void ChangeStateInCrm(DataTable table, string entityName)
        {
            ErrorCounter = 0;
            ThreadPool.QueueUserWorkItem(changeStateInCrm, new object[] { table, entityName });
        }
        private void changeStateInCrm(object o)
        {
            var os = ((object[])o);
            var table = os[0] as DataTable;
            var entityName = os[1] as string;
            var toExecute = new List<Request>();

            table.Rows.Cast<DataRow>().ToList().ForEach(dr =>
            {
                try
                {
                    toExecute.Add(GetRequestByEntity[entityName](dr));
                }
                catch { }
            }
            );
            toExecute.Select((req, r) => new { r, req }).ToList().ForEach(a =>
                                  {
                                      try
                                      {
                                          Service.Execute(a.req);
                                          OnRowUploadCompleted(a.r, null);
                                      }
                                      catch (SoapException ex)
                                      {
                                          Exception = ex;
                                          OnError(ex, a.r, 1);
                                          OnRowUploadCompleted(a.r, ex.Detail.InnerText);
                                          ErrorCounter++;
                                          Error = true;
                                      }
                                      catch (Exception ex)
                                      {
                                          Exception = ex;
                                          OnError(ex, a.r, 1);
                                          OnRowUploadCompleted(a.r, ex.Message);
                                          ErrorCounter++;
                                          Error = true;
                                      }
                                  });
            Finished = true;
        }

        private static Dictionary<string, Func<DataRow, Request>> GetRequestByEntity =
            new Dictionary<string, Func<DataRow, Request>>
                {
                    {
                        "letter", dr => new SetStateLetterRequest
                                            {
                                                EntityId = new Guid(dr["activityid"].ToString()),
                                                LetterState =
                                                    (LetterState)
                                                    Enum.Parse(typeof (LetterState), dr["statecode"].ToString()),
                                                LetterStatus = int.Parse(dr["statuscode"].ToString())
                                            }
                        },
                    {
                        "account", dr => new SetStateAccountRequest
                                             {
                                                 EntityId = new Guid(dr["accountid"].ToString()),
                                                 AccountState =
                                                     (AccountState)
                                                     Enum.Parse(typeof (AccountState), dr["statecode"].ToString()),
                                                 AccountStatus = int.Parse(dr["statuscode"].ToString())
                                             }
                        },
                    {
                        "appointment", dr => new SetStateAppointmentRequest
                                                 {
                                                     EntityId = new Guid(dr["activityid"].ToString()),
                                                     AppointmentState =
                                                         (AppointmentState)
                                                         Enum.Parse(typeof (AppointmentState),
                                                                    dr["statecode"].ToString()),
                                                     AppointmentStatus = int.Parse(dr["statuscode"].ToString())
                                                 }
                        },
                    {
                        "businessunit", dr => new SetStateBusinessUnitRequest
                                                  {
                                                      EntityId = new Guid(dr["businessunitid"].ToString()),
                                                      BusinessUnitState =
                                                          (BusinessUnitState)
                                                          Enum.Parse(typeof (BusinessUnitState),
                                                                     dr["statecode"].ToString()),
                                                      BusinessUnitStatus = int.Parse(dr["statuscode"].ToString())
                                                  }
                        },
                    {
                        "campaign", dr => new SetStateCampaignRequest
                                              {
                                                  EntityId = new Guid(dr["campaignid"].ToString()),
                                                  CampaignState =
                                                      (CampaignState)
                                                      Enum.Parse(typeof (CampaignState), dr["statecode"].ToString()),
                                                  CampaignStatus = int.Parse(dr["statuscode"].ToString())
                                              }
                        },
                    {
                        "campaignactivity", dr => new SetStateCampaignActivityRequest
                                                      {
                                                          EntityId = new Guid(dr["campaignactivityid"].ToString()),
                                                          CampaignActivityState =
                                                              (CampaignActivityState)
                                                              Enum.Parse(typeof (CampaignActivityState),
                                                                         dr["statecode"].ToString()),
                                                          CampaignActivityStatus =
                                                              int.Parse(dr["statuscode"].ToString())
                                                      }
                        },
                    {
                        "campaignrespons", dr => new SetStateCampaignResponseRequest
                                                     {
                                                         EntityId = new Guid(dr["campaignresponsid"].ToString()),
                                                         CampaignResponseState =
                                                             (CampaignResponseState)
                                                             Enum.Parse(typeof (CampaignResponseState),
                                                                        dr["statecode"].ToString()),
                                                         CampaignResponseStatus = int.Parse(dr["statuscode"].ToString())
                                                     }
                        },
                    {
                        "contact", dr => new SetStateContactRequest
                                             {
                                                 EntityId = new Guid(dr["contactid"].ToString()),
                                                 ContactState =
                                                     (ContactState)
                                                     Enum.Parse(typeof (ContactState), dr["statecode"].ToString()),
                                                 ContactStatus = int.Parse(dr["statuscode"].ToString())
                                             }
                        },
                    {
                        "contract", dr => new SetStateContractRequest
                                              {
                                                  EntityId = new Guid(dr["contractid"].ToString()),
                                                  ContractState =
                                                      (ContractState)
                                                      Enum.Parse(typeof (ContractState), dr["statecode"].ToString()),
                                                  ContractStatus = int.Parse(dr["statuscode"].ToString())
                                              }
                        },
                    {
                        "contractdetail", dr => new SetStateContractDetailRequest
                                                    {
                                                        EntityId = new Guid(dr["contractdetailid"].ToString()),
                                                        ContractDetailState =
                                                            (ContractDetailState)
                                                            Enum.Parse(typeof (ContractDetailState),
                                                                       dr["statecode"].ToString()),
                                                        ContractDetailStatus = int.Parse(dr["statuscode"].ToString())
                                                    }
                        },
                    {
                        "discounttype", dr => new SetStateDiscountTypeRequest
                                                  {
                                                      EntityId = new Guid(dr["activityid"].ToString()),
                                                      DiscountTypeState =
                                                          (DiscountTypeState)
                                                          Enum.Parse(typeof (DiscountTypeState),
                                                                     dr["statecode"].ToString()),
                                                      DiscountTypeStatus = int.Parse(dr["statuscode"].ToString())
                                                  }
                        },
                    {
                        "email", dr => new SetStateEmailRequest
                                           {
                                               EntityId = new Guid(dr["activityid"].ToString()),
                                               EmailState =
                                                   (EmailState)
                                                   Enum.Parse(typeof (EmailState), dr["statecode"].ToString()),
                                               EmailStatus = int.Parse(dr["statuscode"].ToString())
                                           }
                        },
                    {
                        "fax", dr => new SetStateFaxRequest
                                         {
                                             EntityId = new Guid(dr["activityid"].ToString()),
                                             FaxState =
                                                 (FaxState) Enum.Parse(typeof (FaxState), dr["statecode"].ToString()),
                                             FaxStatus = int.Parse(dr["statuscode"].ToString())
                                         }
                        },
                    {
                        "incident", dr => new SetStateIncidentRequest
                                              {
                                                  EntityId = new Guid(dr["activityid"].ToString()),
                                                  IncidentState =
                                                      (IncidentState)
                                                      Enum.Parse(typeof (IncidentState), dr["statecode"].ToString()),
                                                  IncidentStatus = int.Parse(dr["statuscode"].ToString())
                                              }
                        },
                    {
                        "incidentresolution", dr => new SetStateIncidentResolutionRequest
                                                        {
                                                            EntityId = new Guid(dr["activityid"].ToString()),
                                                            IncidentResolutionState =
                                                                (IncidentResolutionState)
                                                                Enum.Parse(typeof (IncidentResolutionState),
                                                                           dr["statecode"].ToString()),
                                                            IncidentResolutionStatus =
                                                                int.Parse(dr["statuscode"].ToString())
                                                        }
                        },
                    {
                        "invoice", dr => new SetStateInvoiceRequest
                                             {
                                                 EntityId = new Guid(dr["invoiceid"].ToString()),
                                                 InvoiceState =
                                                     (InvoiceState)
                                                     Enum.Parse(typeof (InvoiceState), dr["statecode"].ToString()),
                                                 InvoiceStatus = int.Parse(dr["statuscode"].ToString())
                                             }
                        },
                    {
                        "kbarticle", dr => new SetStateKbArticleRequest
                                               {
                                                   EntityId = new Guid(dr["kbarticleid"].ToString()),
                                                   KbArticleState =
                                                       (KbArticleState)
                                                       Enum.Parse(typeof (KbArticleState), dr["statecode"].ToString()),
                                                   KbArticleStatus = int.Parse(dr["statuscode"].ToString())
                                               }
                        },
                    {
                        "lead", dr => new SetStateLeadRequest
                                          {
                                              EntityId = new Guid(dr["leadid"].ToString()),
                                              LeadState =
                                                  (LeadState) Enum.Parse(typeof (LeadState), dr["statecode"].ToString()),
                                              LeadStatus = int.Parse(dr["statuscode"].ToString())
                                          }
                        },
                    {
                        "list", dr => new SetStateListRequest
                                          {
                                              EntityId = new Guid(dr["listid"].ToString()),
                                              ListState =
                                                  (ListState) Enum.Parse(typeof (ListState), dr["statecode"].ToString()),
                                              ListStatus = int.Parse(dr["statuscode"].ToString())
                                          }
                        },
                    {
                        "mailmergetemplate", dr => new SetStateMailMergeTemplateRequest
                                                       {
                                                           EntityId = new Guid(dr["mailmergetemplateid"].ToString()),
                                                           MailMergeTemplateState =
                                                               (MailMergeTemplateState)
                                                               Enum.Parse(typeof (MailMergeTemplateState),
                                                                          dr["statecode"].ToString()),
                                                           MailMergeTemplateStatus =
                                                               int.Parse(dr["statuscode"].ToString())
                                                       }
                        },
                    {
                        "opportunity", dr => new SetStateOpportunityRequest
                                                 {
                                                     EntityId = new Guid(dr["opportunityid"].ToString()),
                                                     OpportunityState =
                                                         (OpportunityState)
                                                         Enum.Parse(typeof (OpportunityState),
                                                                    dr["statecode"].ToString()),
                                                     OpportunityStatus = int.Parse(dr["statuscode"].ToString())
                                                 }
                        },
                    {
                        "opportunityclose", dr => new SetStateOpportunityCloseRequest
                                                      {
                                                          EntityId = new Guid(dr["opportunitycloseid"].ToString()),
                                                          OpportunityCloseState =
                                                              (OpportunityCloseState)
                                                              Enum.Parse(typeof (OpportunityCloseState),
                                                                         dr["statecode"].ToString()),
                                                          OpportunityCloseStatus =
                                                              int.Parse(dr["statuscode"].ToString())
                                                      }
                        },
                    {
                        "orderclose", dr => new SetStateOrderCloseRequest
                                                {
                                                    EntityId = new Guid(dr["ordercloseid"].ToString()),
                                                    OrderCloseState =
                                                        (OrderCloseState)
                                                        Enum.Parse(typeof (OrderCloseState), dr["statecode"].ToString()),
                                                    OrderCloseStatus = int.Parse(dr["statuscode"].ToString())
                                                }
                        },
                    {
                        "phonecall", dr => new SetStatePhoneCallRequest
                                               {
                                                   EntityId = new Guid(dr["phonecallid"].ToString()),
                                                   PhoneCallState =
                                                       (PhoneCallState)
                                                       Enum.Parse(typeof (PhoneCallState), dr["statecode"].ToString()),
                                                   PhoneCallStatus = int.Parse(dr["statuscode"].ToString())
                                               }
                        },
                    {
                        "pricelevel", dr => new SetStatePriceLevelRequest
                                                {
                                                    EntityId = new Guid(dr["pricelevelid"].ToString()),
                                                    PriceLevelState =
                                                        (PriceLevelState)
                                                        Enum.Parse(typeof (PriceLevelState), dr["statecode"].ToString()),
                                                    PriceLevelStatus = int.Parse(dr["statuscode"].ToString())
                                                }
                        },
                    {
                        "product", dr => new SetStateProductRequest
                                             {
                                                 EntityId = new Guid(dr["productid"].ToString()),
                                                 ProductState =
                                                     (ProductState)
                                                     Enum.Parse(typeof (ProductState), dr["statecode"].ToString()),
                                                 ProductStatus = int.Parse(dr["statuscode"].ToString())
                                             }
                        },
                    {
                        "quote", dr => new SetStateQuoteRequest
                                           {
                                               EntityId = new Guid(dr["quoteid"].ToString()),
                                               QuoteState =
                                                   (QuoteState)
                                                   Enum.Parse(typeof (QuoteState), dr["statecode"].ToString()),
                                               QuoteStatus = int.Parse(dr["statuscode"].ToString())
                                           }
                        },
                    {
                        "quoteclose", dr => new SetStateQuoteCloseRequest
                                                {
                                                    EntityId = new Guid(dr["quotecloseid"].ToString()),
                                                    QuoteCloseState =
                                                        (QuoteCloseState)
                                                        Enum.Parse(typeof (QuoteCloseState), dr["statecode"].ToString()),
                                                    QuoteCloseStatus = int.Parse(dr["statuscode"].ToString())
                                                }
                        },
                    {
                        "relationshiprole", dr => new SetStateRelationshipRoleRequest
                                                      {
                                                          EntityId = new Guid(dr["relationshiproleid"].ToString()),
                                                          RelationshipRoleState =
                                                              (RelationshipRoleState)
                                                              Enum.Parse(typeof (RelationshipRoleState),
                                                                         dr["statecode"].ToString()),
                                                          RelationshipRoleStatus =
                                                              int.Parse(dr["statuscode"].ToString())
                                                      }
                        },
                    {
                        "salesorder", dr => new SetStateSalesOrderRequest
                                                {
                                                    EntityId = new Guid(dr["salesorderid"].ToString()),
                                                    SalesOrderState =
                                                        (SalesOrderState)
                                                        Enum.Parse(typeof (SalesOrderState), dr["statecode"].ToString()),
                                                    SalesOrderStatus = int.Parse(dr["statuscode"].ToString())
                                                }
                        },
                    {
                        "sdkmessageprocessingstep", dr => new SetStateSdkMessageProcessingStepRequest
                                                              {
                                                                  EntityId = new Guid(dr["sdkmessageprocessingstepid"].ToString()),
                                                                  SdkMessageProcessingStepState =
                                                                      (SdkMessageProcessingStepState)
                                                                      Enum.Parse(
                                                                          typeof (SdkMessageProcessingStepState),
                                                                          dr["statecode"].ToString()),
                                                                  SdkMessageProcessingStepStatus =
                                                                      int.Parse(dr["statuscode"].ToString())
                                                              }
                        },
                    {
                        "serviceappointment", dr => new SetStateServiceAppointmentRequest
                                                        {
                                                            EntityId = new Guid(dr["serviceappointmentid"].ToString()),
                                                            ServiceAppointmentState =
                                                                (ServiceAppointmentState)
                                                                Enum.Parse(typeof (ServiceAppointmentState),
                                                                           dr["statecode"].ToString()),
                                                            ServiceAppointmentStatus =
                                                                int.Parse(dr["statuscode"].ToString())
                                                        }
                        },
                    {
                        "systemuser", dr => new SetStateSystemUserRequest
                                                {
                                                    EntityId = new Guid(dr["systemuserid"].ToString()),
                                                    SystemUserState =
                                                        (SystemUserState)
                                                        Enum.Parse(typeof (SystemUserState), dr["statecode"].ToString()),
                                                    SystemUserStatus = int.Parse(dr["statuscode"].ToString())
                                                }
                        },
                    {
                        "task", dr => new SetStateTaskRequest
                                          {
                                              EntityId = new Guid(dr["taskid"].ToString()),
                                              TaskState =
                                                  (TaskState) Enum.Parse(typeof (TaskState), dr["statecode"].ToString()),
                                              TaskStatus = int.Parse(dr["statuscode"].ToString())
                                          }
                        },
                    {
                        "transactioncurrency", dr => new SetStateTransactionCurrencyRequest
                                                         {
                                                             EntityId = new Guid(dr["transactioncurrencyid"].ToString()),
                                                             TransactionCurrencyState =
                                                                 (TransactionCurrencyState)
                                                                 Enum.Parse(typeof (TransactionCurrencyState),
                                                                            dr["statecode"].ToString()),
                                                             TransactionCurrencyStatus =
                                                                 int.Parse(dr["statuscode"].ToString())
                                                         }
                        },
                    {
                        "userquery", dr => new SetStateUserQueryRequest
                                               {
                                                   EntityId = new Guid(dr["userqueryid"].ToString()),
                                                   UserQueryState =
                                                       (UserQueryState)
                                                       Enum.Parse(typeof (UserQueryState), dr["statecode"].ToString()),
                                                   UserQueryStatus = int.Parse(dr["statuscode"].ToString())
                                               }
                        },
                    {
                        "workflow", dr => new SetStateWorkflowRequest
                                              {
                                                  EntityId = new Guid(dr["workflowid"].ToString()),
                                                  WorkflowState =
                                                      (WorkflowState)
                                                      Enum.Parse(typeof (WorkflowState), dr["statecode"].ToString()),
                                                  WorkflowStatus = int.Parse(dr["statuscode"].ToString())
                                              }
                        }
                };

        private static Dictionary<string, Func<string, Property>> MapStringToProperties =
            new Dictionary<string, Func<string, Property>>
                {
                    { "Owner" , val => new OwnerProperty { Value = new Owner (val.Substring(0,val.IndexOf(',')), new Guid(val.Substring(val.IndexOf(',')+1))) } },
                    { "Status", val => string.IsNullOrEmpty(val)
                                       ? new StatusProperty { Value = new Status { IsNull = true, IsNullSpecified = true } }
                                       : new StatusProperty { Value = new Status { Value = Convert.ToInt32(val), IsNull = false, IsNullSpecified = true } } },
                    { "Picklist", val => string.IsNullOrEmpty(val)
                                         ? new PicklistProperty { Value = new Picklist { IsNull = true, IsNullSpecified = true } }
                                         : new PicklistProperty { Value = new Picklist { Value=Convert.ToInt32(val), IsNull = false, IsNullSpecified = true } } },
                    { "UniqueIdentifier", val => new UniqueIdentifierProperty { Value = string.IsNullOrEmpty(val) ? new UniqueIdentifier() : new UniqueIdentifier(new Guid(val)) } },
                    { "String", val => new StringProperty { Value = val } },
                    { "State", val => new StateProperty { Value = val } },
                    { "Lookup", val =>  string.IsNullOrEmpty(val)
                                        ? new LookupProperty { Value = new Lookup { IsNull = true, IsNullSpecified = true } }
                                        : new LookupProperty { Value = new Lookup(val.Substring(0,val.IndexOf(',')), new Guid(val.Substring(val.IndexOf(',')+1))) } } ,
                    { "PrimaryKey", val => new KeyProperty { Value = new Key(new Guid(val)) } },
                    { "Key", val => new KeyProperty { Value = new Key(new Guid(val)) } },
                    { "Customer", val => new CustomerProperty { Value = new Customer(val.Substring(0,val.IndexOf(',')), new Guid(val.Substring(val.IndexOf(',')+1))) } },
                    { "Number", val => new CrmNumberProperty { Value = new CrmNumber(Convert.ToInt32(string.IsNullOrEmpty(val)? "0":val)) { IsNull = false, IsNullSpecified = true } } },
                    { "Integer", val => new CrmNumberProperty { Value = new CrmNumber(Convert.ToInt32(string.IsNullOrEmpty(val)? "0":val)) { IsNull = false, IsNullSpecified = true } } },
                    { "Money", val => new CrmMoneyProperty { Value = new CrmMoney(Convert.ToDecimal(string.IsNullOrEmpty(val)? "0":val)) { IsNull = false, IsNullSpecified = true } } },
                    { "Decimal", val => new CrmDecimalProperty { Value = new CrmDecimal(Convert.ToDecimal(string.IsNullOrEmpty(val)? "0":val)) { IsNull = false, IsNullSpecified = true } } },
                    { "Float", val => new CrmFloatProperty { Value = new CrmFloat(Convert.ToDouble(string.IsNullOrEmpty(val)? "0":val)) { IsNull = false, IsNullSpecified = true } } },
                    { "DateTime", val => string.IsNullOrEmpty(val) 
                                         ? new CrmDateTimeProperty { Value = new CrmDateTime { IsNull = true, IsNullSpecified = true } }
                                         : new CrmDateTimeProperty { Value = new CrmDateTime(val) { IsNull = false, IsNullSpecified = true } } },
                    { "Boolean", val => new CrmBooleanProperty { Value = new CrmBoolean(val == "1") { IsNull = false, IsNullSpecified = true } } },
                    { "PartyList", val => {
                        Property prop;
                        if (string.IsNullOrEmpty(val))
                        {
                            prop = new DynamicEntityArrayProperty();
                        }
                        else
                        {
                            var a = val.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                            var dynarr = a.ToList()
                                          .Select(s =>
                                                    {
                                                        var retval = new DynamicEntity(s.Substring(0, s.IndexOf("{")));
                                                        var props = s.Substring(s.IndexOf("{") + 1).Split(new[] { '(', ')' }, StringSplitOptions.RemoveEmptyEntries);
                                                        var b = props.ToList().Where(p => p != "}").Select(
                                                            p =>
                                                            {
                                                                var x = p.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                                                                var value = x[2] + (x.Length == 4 ? "," + x[3] : "");
                                                                var subProp = MapStringToProperties[x[1].Replace("Property", "").Replace("Crm", "")](value);
                                                                subProp.Name = x[0];
                                                                return subProp;
                                                            }).ToArray();
                                                        retval.Properties.AddRange(b);

                                                        return retval;
                                                    })
                                          .ToArray();
                            prop = new DynamicEntityArrayProperty { Value = dynarr };
                                    
                        }
                    return prop;}}};


        public void DeleteFromCrm(QueryItem query, string entityName)
        {
            var res = MessageBox.Show("This will create a new Bulk deletion job. Are you sure you want to continue?",
                            "Bulk Deletion job", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);

            if (res == DialogResult.Yes)
            {
                var response = (WhoAmIResponse)Service.Execute(new WhoAmIRequest());
                var currentUserId = response.UserId;

                QueryExpression bulkDeleteQuery;
                if (query != null)
                {
                    var exp = (FetchXmlToQueryExpressionResponse)Service.Execute(new FetchXmlToQueryExpressionRequest { FetchXml = query.SavedQueryXml });
                    bulkDeleteQuery = exp.Query;
                }
                else
                {
                    bulkDeleteQuery = new QueryExpression(entityName)
                                          {
                                              Distinct = false,
                                              ColumnSet = new AllColumns()
                                          };
                }
                var bulkDeleteRequest = new BulkDeleteRequest
                                            {
                                                JobName = string.Format("Bulk Deletion - {1} - {0:yyyyMMdd hhmmss}", DateTime.Now, entityName + (query == null ? "" : " " + query.SavedQueryName)),
                                                QuerySet = new QueryBase[] { bulkDeleteQuery },
                                                StartDateTime = new CrmDateTime(DateTime.Now.ToString("s")),
                                                RecurrencePattern = String.Empty,
                                                SendEmailNotification = false,
                                                ToRecipients = new Guid[0],//[] { currentUserId },
                                                CCRecipients = new Guid[0]
                                            };
                Service.Execute(bulkDeleteRequest);
            }
        }
    }
}
